/*
 * BufferManager.cpp
 *
 *  Created on: 09-Oct-2013
 *      Author: pridhvi
 */

#include "BufferManager.h"
#include "../PageLayout/DBMetaData.h"
#include "../dependency/debug.h"
#include "../PageLayout/DirHeader.h"
#include "../Sysfiles/SysCatalog.h"
//#include "../dependency/ExternDefsOfGlobalConstants.h"
#include "../dependency/ExternDefOfGlobalVariables.h"
#include "../dependency/ErrorLookupTable.h"
#include <string>

BufferManager *BufferManager::bmInstance_;

BufferManager::BufferManager() {
    // TODO Auto-generated constructor stub
    bufferPool_ = NULL;
    initializeBuffer(0);
    isDBOpen = false;
    LRUReplacement = new ReplacementPolicy();
}

BufferManager::BufferManager(int sizeMB) {
    // TODO Auto-generated constructor stub
    bufferPool_ = NULL;
    initializeBuffer(sizeMB);
    isDBOpen = false;
    LRUReplacement = new ReplacementPolicy();
}

BufferManager::~BufferManager() {
    // TODO Auto-generated destructor stub
    if (bufferPool_ != NULL)
        deleteBufferPool();
    //delete LRUReplacement;
}

BufferManager* BufferManager::getInstance() {
    if (bmInstance_ == NULL)
        bmInstance_ = new BufferManager();
    return bmInstance_;
}

//Initialising buffer pool,noOfFrames etc and all other variables to 0

STATUS_CODE BufferManager::initializeBuffer(int sizeInMB) {

    if (bufferPool_ != NULL) {
        delete[] bufferPool_;
        bufferPool_ = NULL;
    }

    noOfDiskAccess = 0;
    noOfPgRead = 0;
    noOfTimesPgFoundInBuffer = 0;
    totalReqToBuffer = 0;
    pageSize = PAGE_SIZE;
    bufferSize_ = sizeInMB;

    noOfFrames = sizeInMB * 1024 * 1024 / pageSize;
    if (noOfFrames > 0) {
        bufferPool_ = new Frame*[noOfFrames];
        for (int i = 0; i < noOfFrames; i++) {
            bufferPool_[i] = new Frame(pageSize);
        }
        DEBUG("Buffer initailized with :" << noOfFrames);
    }
    frameLookUp.clear();
    return SUCCESS;

}

STATUS_CODE BufferManager::createDB(string dbName, int numOfPages) {
    /***
     *
     */
    if (isDBOpen == true) {
        return CLOSE_OPENED_DATABASE;
    }
    initializeBuffer(bufferSize_);
    int err = diskMan.createDataBase(dbName, numOfPages);
    if (err != SUCCESS)
        return err;
    return SUCCESS;

}

STATUS_CODE BufferManager::useDB(string dbName) {
    DEBUG("useDB: " << dbName);
    int err = diskMan.openDataBase(dbName);
    if (err != SUCCESS)
        return err;
    isDBOpen = true;
    noOfDiskAccess = 0;
    noOfPgRead = 0;
    //	char pgData[PAGE_SIZE];

    //	diskMan.readPage(PAGE_SIZE, pgData, 0);
    //
    //	if (err != SUCCESS)
    //		return err;
    //	DBMetaData dbMetaData(pgData);
    //	int pageSize = dbMetaData.getPageSizeOfDatabase();
    //	DEBUG("open databse:" << dbName << "  page size:"<<pageSize);
    return SUCCESS;
}

STATUS_CODE BufferManager::resizeDB(int noOfPgs) {
    int err = diskMan.resizeDataBase(noOfPgs);
    if (err != SUCCESS)
        return err;
    return SUCCESS;
}

STATUS_CODE BufferManager::dropDB(string dbName) {
    return diskMan.dropDataBase(dbName);

}

STATUS_CODE BufferManager::getPage(int pageNo, char*& pgData) {
    // look in hashmap whether page is in buffer or not
    // if not then search for free frame
    DEBUG(" inside loading and pinning: " << pageNo);
    if (isDBOpen == false)
        return DATABASE_NOT_OPEN;
    totalReqToBuffer++;
    int err = FAILURE;
    if (!IS_CACHE_ON) {
        if (pageAddressLookUp.find(pageNo) != pageAddressLookUp.end()) {
            pgData = pageAddressLookUp[pageNo];
            pageAddressInUseLookUp[pageNo]++;
            return SUCCESS;
        }
        pageAddressInUseLookUp[pageNo] = 1;
        char * pageData = (char*) malloc(PAGE_SIZE * sizeof (char));
        pageAddressLookUp[pageNo] = pageData;
        int err = diskMan.readPage(PAGE_SIZE, pageData, pageNo); // reading data into frame
        if (err != SUCCESS) {
            DEBUG("Error:" << err);
            return err;
        }
        noOfDiskAccess++;
        pgData = pageData;

        return SUCCESS;
    }
    int frameNo = getFrame(pageNo);
    DEBUG(" got the frame: " << frameNo);
    if (frameNo != FRAME_NOT_FOUND) { // page found in buffer pool
        pgData = bufferPool_[frameNo]->pageData;
        noOfTimesPgFoundInBuffer++;
        DEBUG("Page found in Buffer :" << pageNo);
    } else {
        frameNo = getFreeFrame(); // for getting the free frame using LRU
        DEBUG("After getting freeframe :" << frameNo);
        if (frameNo == FRAME_NOT_FOUND)
            return BUFFER_FULL;
        else {
            // check whether the frame is dirty if s then write it up to hard disk
            // remove it from look up table
            // read the required page into frame and make entry in look up
            if (bufferPool_[frameNo]->isDirty_ == true) {
                int pageToBeReturned = bufferPool_[frameNo]->pageNo;
                DEBUG("Page selected from BufferPool :" << pageToBeReturned);
                char *pageData = bufferPool_[frameNo]->pageData;
                err = diskMan.writePage(PAGE_SIZE, pageData, pageToBeReturned); // writing data from frame
                if (err != SUCCESS)
                    return err;
            }
            frameLookUp.erase(bufferPool_[frameNo]->pageNo); // key is framenumber
            err = diskMan.readPage(PAGE_SIZE, bufferPool_[frameNo]->pageData,
                    pageNo); // reading data into frame
            if (err != SUCCESS) {
                DEBUG("Error:" << err);
                return err;
            }
            noOfDiskAccess++;
            pgData = bufferPool_[frameNo]->pageData;
            DEBUG("loaded page : " << pageNo);
            insertIntoFrameLookUp(pageNo, frameNo);
        }
    }
    LRUReplacement->increasePriority(frameNo);
    bufferPool_[frameNo]->pinCount_++;
    noOfPgRead++;
    return SUCCESS;
}

int BufferManager::getFrame(int pageNo) {
    map<int, int>::iterator itr = frameLookUp.find(pageNo);
    if (itr != frameLookUp.end())
        return itr->second;
    return FRAME_NOT_FOUND;
}

int BufferManager::getFreeFrame() {
    // used to retrieve frame
    DEBUG("Entering getFreeFrame..");
    for (int i = 0; i < noOfFrames; i++) {
        if (bufferPool_[i]->pinCount_ == -1) {
            bufferPool_[i]->pinCount_++;
            DEBUG("Exiting getFreeFrame :" << i);
            return i;
        }
    }
    // if no free frame found need to perform page replacement
    int swapOutFrame = LRUReplacement->getSwappingFrame();
    DEBUG("Swapping out frame num :" << swapOutFrame);
    LRUReplacement->resetPriority(swapOutFrame);
    DEBUG("Exiting getFreeFrame :" << swapOutFrame);
    return swapOutFrame;
}

void BufferManager::releasePage(int pageNo, bool dirty) {
    DEBUG("using pindown:" << pageNo);
    if (!IS_CACHE_ON) {
        if(pageAddressLookUp.find(pageNo) == pageAddressLookUp.end()) return ;//page already release by flushAll
        char* pageData = pageAddressLookUp[pageNo];
        pageAddressInUseLookUp[pageNo]--;
        if (pageAddressInUseLookUp[pageNo] > 0) return;
        int err = diskMan.writePage(PAGE_SIZE, pageData, pageNo);
        if (err != SUCCESS)
            DEBUG("inside release page :" << ErrorLookupTable::lookup(err));
        pageAddressLookUp.erase(pageNo);
        pageAddressInUseLookUp.erase(pageNo);
        delete pageData;
        noOfDiskAccess++;

        return; // SUCCESS;        
    }
    int frameNo = getFrame(pageNo);
    //aasserts
    bufferPool_[frameNo]->pinCount_--;
    if (dirty == true && bufferPool_[frameNo]->isDirty_ == false)
        bufferPool_[frameNo]->isDirty_ = dirty;

}

STATUS_CODE BufferManager::getNewPage(int& pageNo, char*& pageData) {
    DEBUG("Inside getNewPage..");
    if (isDBOpen == false)
        return DATABASE_NOT_OPEN;
    //DEBUG("getnew page :" << pageNo);
    int err = freePagesLL.getFreePageNo(pageNo);
    DEBUG("getnew page :" << pageNo);
    if (err != SUCCESS)
        return err;
    return getPage(pageNo, pageData);
}

STATUS_CODE BufferManager::closeDB() {
    if (isDBOpen == true) {
        flushAll();
        DEBUG("ALL FLUSHED");
        int err = diskMan.closeDataBase();
        if (err != SUCCESS)
            return err;
        isDBOpen = false;
        return SUCCESS;
    }
    return DATABASE_NOT_OPEN;
}

void BufferManager::insertIntoFrameLookUp(int pageNo, int frameNo) {
    frameLookUp[pageNo] = frameNo;
    bufferPool_[frameNo]->pageNo = pageNo;
}

STATUS_CODE BufferManager::freePage(int pgNo) {
    FreePageLinkedList llPgManager;
    int err = llPgManager.addToFreeList(pgNo);
    if (err != SUCCESS)
        return err;
    return SUCCESS;
}

STATUS_CODE BufferManager::flushPageToDisk(int pageNo, char* pgData) {
    noOfDiskAccess++;
    int err = diskMan.writePage(PAGE_SIZE, pgData, pageNo);
    if (err != SUCCESS)
        return err;
    return SUCCESS;
}

STATUS_CODE BufferManager::flushPageToDisk(int pageNo) {
    int frameNo = getFrame(pageNo);
    int err = diskMan.writePage(PAGE_SIZE, bufferPool_[frameNo]->pageData,
            pageNo);
    if (err != SUCCESS)
        return err;
    return SUCCESS;
}

STATUS_CODE BufferManager::flushAll() {
    int err = SUCCESS;
    if (IS_CACHE_ON) {
        for (int i = 0; i < noOfFrames - 1; i++) {
            if (bufferPool_[i]->isDirty_ == true) {
                err = flushPageToDisk(bufferPool_[i]->pageNo,
                        bufferPool_[i]->pageData);
                bufferPool_[i]->isDirty_ = false;
                if (err != SUCCESS)
                    return err;
            }
        }
    } else {
        for (map<int, char *> ::iterator itr = pageAddressLookUp.begin(); itr != pageAddressLookUp.end(); itr++) {
        //    cout << itr->first << ":" << itr->second << endl;
            pageAddressInUseLookUp[itr->first]=0;
            releasePage(itr->first,true);
        }

    }
    return SUCCESS;
}

STATUS_CODE BufferManager::getPageNum(int frameNum, int & pgNo) {
    if (isDBOpen == false)
        return DATABASE_NOT_OPEN;
    //DBMetaData dbPageHeader(0); //DB_MAIN_HEADER_PAGE
    pgNo = bufferPool_[frameNum]->pageNo;
    //    if (pgNo < 0 || pgNo > dbPageHeader.getTotalNumberOfPages())
    //        return INVALID_PAGE;
    return SUCCESS;

}

float BufferManager::getHitRate() {
    float hitRate = (float) (noOfTimesPgFoundInBuffer)
            / (float) (totalReqToBuffer);
    return hitRate;
}

map<string, int> BufferManager::listDB() {
    DEBUG("inside listfile of Buffer manager");
    map<string, int> files = diskMan.listDB();
    return files;
}

STATUS_CODE BufferManager::startCache(int sizeInMB = 200) {
    IS_CACHE_ON = true;
    initializeBuffer(sizeInMB);
    return SUCCESS;
}

STATUS_CODE BufferManager::stopCache() {
    flushAll();
    IS_CACHE_ON = false;
    if (bufferPool_ != NULL)
        deleteBufferPool();
    initializeBuffer(0);
    return SUCCESS;

}

STATUS_CODE BufferManager::deleteBufferPool() {
    //    for (int i = 0; i < noOfFrames; i++) {
    //        delete bufferPool_[i];
    //    }
    delete[] bufferPool_;
    return SUCCESS;
}

STATUS_CODE BufferManager::commitCache() {
    flushAll();
    return SUCCESS;
}

STATUS_CODE BufferManager::displayCache() {
    //data member 
    //frame no pageno dirty priority  
    for (int i = 0; i < noOfFrames; i++) {
        int pg;
        getPageNum(i, pg);
        cout << "\nF: " << i << " P: " << bufferPool_[i]->pageNo << " D:  " << bufferPool_[i]->isDirty_ << " Pin: " << bufferPool_[i]->pinCount_ << " Pri: " << bufferPool_[i]->priority_;
    }

    return SUCCESS;
}

STATUS_CODE BufferManager::displayframe(int frameNo) {
    cout << "Frame Number:" << frameNo << endl;
    cout << "Page Number:" << bufferPool_[frameNo]->pageNo << endl;
    cout << "IsDirty:" << bufferPool_[frameNo]->isDirty_ << endl;
    cout << "PinCount:" << bufferPool_[frameNo]->pinCount_ << endl;
    cout << "Priority:" << bufferPool_[frameNo]->priority_ << endl;
    cout << "PageData:" << bufferPool_[frameNo]->pageData << endl;
    return SUCCESS;

}

